package net.csiit.platform.codeguard.classloader;

import net.csiit.platform.codeguard.spring.archive.JarFileArchive;
import net.csiit.platform.codeguard.util.ArchiveUtil;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.Set;

/**
 * @author adam
 * @date 2020/7/3
 */
public class ClassLoaderFactory {

    private static final ClassLoaderFactory instance = new ClassLoaderFactory();
    private DecryptSpringBootClassLoader cachedClassLoader;

    public static ClassLoaderFactory getInstance() {
        return instance;
    }

    private ClassLoaderFactory() {
        File currentJar = null;
        try {
            currentJar = determineCurrentJar();
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            CodeGuardConfiguration.loadConfig(currentJar == null ? null : currentJar.getParent());
        } catch (Exception e) {
            throw new RuntimeException("Load code guard configuration failed !", e);
        }
    }

    public synchronized DecryptSpringBootClassLoader getOrCreateClassLoader() throws Exception {
        if (cachedClassLoader == null) {
            cachedClassLoader = getSpringBootDecryptClassLoader();
        }

        return cachedClassLoader;
    }

    public DecryptSpringBootClassLoader getSpringBootDecryptClassLoader() throws Exception {
        return getSpringBootDecryptClassLoader(Thread.currentThread().getContextClassLoader());
    }

    public DecryptSpringBootClassLoader getSpringBootDecryptClassLoader(ClassLoader parent) throws Exception {
        return getSpringBootDecryptClassLoader(new File(CodeGuardConfiguration.getBootFatJar()), parent, CodeGuardConfiguration.getPackages(), CodeGuardConfiguration.getKey());
    }

    public DecryptSpringBootClassLoader getSpringBootDecryptClassLoader(File bootJarFile, ClassLoader parent, Set<String> decryptPackages, String decryptKey) throws Exception {
        if (bootJarFile.exists() && bootJarFile.isFile() && bootJarFile.getName().endsWith(".jar")) {
            JarFileArchive archive = new JarFileArchive(bootJarFile);
            List<URL> urls = ArchiveUtil.getClassPathUrls(archive);
            return new DecryptSpringBootClassLoader(urls.toArray(new URL[0]), parent, decryptPackages, decryptKey);
        }

        throw new IllegalArgumentException("Unknown spring boot jar file : " + bootJarFile.getAbsolutePath());
    }

    protected final File determineCurrentJar() throws Exception {
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
        String path = (location != null) ? location.getSchemeSpecificPart() : null;
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        }
        File root = new File(path);
        if (!root.exists()) {
            throw new IllegalStateException("Unable to determine code source archive from " + root);
        }

        return root;
    }

}
